﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
using Go;
using GoRpc;

namespace FileService
{
    [Rpc.Methods.AllowBind]
    class IOController
    {
        [Rpc.Methods.AllowBind(rename: "token")] public long token;
        [Rpc.Methods.AllowBind(rename: "block")] public int block = 1024;
        public shared_strand ioStrand;
        public Dictionary<long, Rpc.Session> sessDict;

        [Rpc.Methods.AllowBind(rename: "GetDrives")]
        public ValueTask<string[]> GetDrives()
        {
            return generator.send_strand(ioStrand, delegate ()
            {
                return Array.ConvertAll(DriveInfo.GetDrives(), (DriveInfo drive) => drive.Name);
            });
        }

        [Rpc.Methods.AllowBind(rename: "GetFiles")]
        public ValueTask<string[]> GetFiles(string path)
        {
            return generator.send_strand(ioStrand, delegate ()
            {
                return Array.ConvertAll(Directory.GetFiles(path), (string v) => Path.GetFileName(v));
            });
        }

        [Rpc.Methods.AllowBind(rename: "GetDirectories")]
        public ValueTask<string[]> GetDirectories(string path)
        {
            return generator.send_strand(ioStrand, delegate ()
            {
                return Array.ConvertAll(Directory.GetDirectories(path), (string v) => Path.GetFileName(v));
            });
        }

        [Rpc.Methods.AllowBind(rename: "FileSize")]
        public ValueTask<long> FileSize(string path)
        {
            return generator.send_strand(ioStrand, delegate ()
            {
                return new FileInfo(path).Length;
            });
        }

        [Rpc.Methods.AllowBind(rename: "FileInfo")]
        public ValueTask<DateTime[]> FileInfo(string path)
        {
            return generator.send_strand(ioStrand, delegate ()
            {
                return new DateTime[]
                {
                    File.GetLastWriteTime(path),
                    File.GetCreationTime(path),
                    File.GetLastAccessTime(path),
                };
            });
        }

        [Rpc.Methods.AllowBind(rename: "DirectoryInfo")]
        public ValueTask<DateTime[]> DirectoryInfo(string path)
        {
            return generator.send_strand(ioStrand, delegate ()
            {
                return new DateTime[]
                {
                    Directory.GetLastWriteTime(path),
                    Directory.GetCreationTime(path),
                    Directory.GetLastAccessTime(path),
                };
            });
        }

        [Rpc.Methods.AllowBind(rename: "SetDirectoryInfo")]
        public Task SetDirectoryInfo(string path, DateTime[] dirInfo)
        {
            return generator.send_strand(ioStrand, delegate ()
            {
                Directory.SetLastWriteTime(path, dirInfo[0]);
                Directory.SetCreationTime(path, dirInfo[1]);
                Directory.SetLastAccessTime(path, dirInfo[2]);
            });
        }

        [Rpc.Methods.AllowBind(rename: "ReadTest")]
        public async Task ReadTest(Rpc.Methods.Name sendData)
        {
            Console.WriteLine($"Begin ReadTest {token}");
            try
            {
                Rpc.Session session = Rpc.Session.Self;
                generator self = generator.self;
                byte[] data = new byte[block * 1024];
                Rpc.Session.CancelWait().AppendNotify(() => self.stop());
                Rpc.Session.Self.OverSend(long.MaxValue, (_1, _2) => Rpc.Session.Send(data, 0, data.Length));
                await session.Call(sendData);
            }
            finally
            {
                Console.WriteLine($"End ReadTest {token}");
            }
        }

        [Rpc.Methods.AllowBind(rename: "WriteTest")]
        public async Task WriteTest()
        {
            Console.WriteLine($"Begin WriteTest {token}");
            try
            {
                Rpc.OverRecv[] recvs = Rpc.Session.OverRecv();
                byte[] data = new byte[block * 1024];
                while (!recvs[0].Completed)
                {
                    await recvs[0].Recv(data, 0, data.Length);
                }
            }
            finally
            {
                Console.WriteLine($"End WriteTest {token}");
            }
        }

        [Rpc.Methods.AllowBind(rename: "CallTest")]
        public void CallTest()
        {
        }

        [Rpc.Methods.AllowBind(rename: "ReadFile")]
        public async Task<bool> ReadFile(string path, Rpc.Methods.Name writeFile)
        {
            Rpc.Session session = Rpc.Session.Self;
            generator self = generator.self;
            Rpc.Session.CancelWait().AppendNotify(() => self.stop());
            FileStream file = null;
            DateTime[] info = null;
            try
            {
                generator.lock_stop();
                await generator.send_strand(ioStrand, delegate ()
                {
                    try
                    {
                        file = File.OpenRead(path);
                        info = new DateTime[]
                        {
                            File.GetCreationTime(path),
                            File.GetLastWriteTime(path),
                            File.GetLastAccessTime(path),
                        };
                    }
                    catch (Exception) { }
                });
                generator.unlock_stop();
                if (null == file)
                {
                    return false;
                }
                Console.WriteLine($"Begin Send {path}");
                long fileLength = file.Length;
                if (fileLength > 0)
                {
                    Rpc.SendDoubleBuffer dbuff = new Rpc.SendDoubleBuffer(block * 1024, ioStrand);
                    dbuff.Async(() => file.Read(dbuff.ReadBuff, 0, (int)Math.Min(dbuff.ReadBuff.Length, fileLength)));
                    Rpc.Session.Self.OverSend(fileLength, async delegate (long offset, long count)
                    {
                        await dbuff.Exchange();
                        if (dbuff.Error)
                        {
                            return;
                        }
                        var data = dbuff.SendData;
                        if (count - data.Count > 0)
                        {
                            dbuff.Async(() => file.Read(dbuff.ReadBuff, 0, (int)Math.Min(dbuff.ReadBuff.Length, count - data.Count)));
                        }
                        await Rpc.Session.Send(data);
                    });
                }
                var res = await session.Call(writeFile, info);
                if (!res.Success)
                {
                    Console.WriteLine($"Fail Send {path}");
                    return false;
                }
                Console.WriteLine($"End Send {path}");
                return true;
            }
            catch (generator.stop_exception)
            {
                Console.WriteLine($"Cancel Send {path}");
                throw;
            }
            finally
            {
                file?.Dispose();
            }
        }

        [Rpc.Methods.AllowBind(rename: "ReadFileMT")]
        public async Task<bool> ReadFileMT(long[] sessToken, string path, Rpc.Methods.Name writeFile)
        {
            generator self = generator.self;
            Rpc.Session.CancelWait().AppendNotify(() => self.stop());
            FileStream file = null;
            try
            {
                generator.lock_stop();
                await generator.send_strand(ioStrand, delegate ()
                {
                    try
                    {
                        file = File.OpenRead(path);
                    }
                    catch (Exception) { }
                });
                generator.unlock_stop();
                if (null == file)
                {
                    return false;
                }
                Console.WriteLine($"Begin Send {path}");
                bool completed = await TransmitFile.Send(block, writeFile, file, Array.ConvertAll(sessToken, (long v) => sessDict[v]), ioStrand, null);
                if (!completed)
                {
                    Console.WriteLine($"Fail Send {path}");
                    return false;
                }
                Console.WriteLine($"End Send {path}");
                return true;
            }
            catch (generator.stop_exception)
            {
                Console.WriteLine($"Cancel Send {path}");
                throw;
            }
            finally
            {
                file?.Dispose();
            }
        }

        [Rpc.Methods.AllowBind(rename: "CreateDirectory")]
        public Task CreateDirectory(string path)
        {
            Console.WriteLine($"CreateDirectory {path}");
            return generator.send_strand(ioStrand, delegate ()
            {
                Directory.CreateDirectory(path);
            });
        }

        [Rpc.Methods.AllowBind(rename: "CreateFile")]
        public Task CreateFile(string path)
        {
            Console.WriteLine($"CreateFile {path}");
            return generator.send_strand(ioStrand, delegate ()
            {
                File.Create(path).Dispose();
            });
        }

        [Rpc.Methods.AllowBind(rename: "WriteFile")]
        public async Task<bool> WriteFile(bool force, string dstFile, DateTime[] fileInfo)
        {
            if (!force && File.Exists(dstFile))
            {
                Console.WriteLine($"{dstFile} 已存在");
                return false;
            }
            Rpc.OverRecv[] recvs = Rpc.Session.OverRecv();
            FileStream file = null;
            bool completed = false;
            try
            {
                Console.WriteLine($"Begin Recv {dstFile}");
                generator.lock_stop();
                await generator.send_strand(ioStrand, delegate ()
                {
                    try
                    {
                        if (File.Exists(dstFile))
                        {
                            if (File.GetLastWriteTime(dstFile) >= fileInfo[0])
                            {
                                Console.WriteLine($"{dstFile} 忽略\r\n");
                                completed = true;
                                return;
                            }
                            else
                            {
                                Console.WriteLine($"{dstFile} 覆盖\r\n");
                            }
                        }
                        file = File.Open(dstFile, FileMode.Create);
                    }
                    catch (Exception) { }
                });
                generator.unlock_stop();
                if (null == file)
                {
                    return completed;
                }
                if (null != recvs)
                {
                    Rpc.RecvDoubleBuffer dbuff = new Rpc.RecvDoubleBuffer(block * 1024, ioStrand);
                    bool first = true;
                    long offset = 0;
                    for (offset = 0; offset < recvs[0].Length;)
                    {
                        int len = await recvs[0].Recv(dbuff.RecvBuff, 0, dbuff.RecvBuff.Length);
                        if (len <= 0)
                        {
                            break;
                        }
                        await dbuff.Exchange();
                        if (dbuff.Error)
                        {
                            break;
                        }
                        dbuff.Async(delegate ()
                        {
                            if (first)
                            {
                                first = false;
                                if (0 == string.Compare(new DriveInfo(dstFile).DriveFormat, "NTFS", true))
                                {
                                    file.SetLength(recvs[0].Length);
                                }
                            }
                            file.Write(dbuff.WriteData, 0, len);
                        });
                        offset += len;
                    }
                    await dbuff.Exchange();
                    if (!dbuff.Error)
                    {
                        completed = offset == recvs[0].Length;
                    }
                    if (recvs[0].Cancel)
                    {
                        Console.WriteLine($"Cancel Recv {dstFile}");
                    }
                    else
                    {
                        Console.WriteLine($"End Recv {dstFile}");
                    }
                    return !recvs[0].Cancel;
                }
                else
                {
                    completed = true;
                    return true;
                }
            }
            finally
            {
                generator.lock_stop();
                await generator.send_strand(ioStrand, delegate ()
                {
                    if (null != file)
                    {
                        try
                        {
                            file.Dispose();
                            if (completed)
                            {
                                File.SetLastWriteTime(dstFile, fileInfo[0]);
                                File.SetCreationTime(dstFile, fileInfo[1]);
                                File.SetLastAccessTime(dstFile, fileInfo[2]);
                            }
                            else
                            {
                                File.Delete(dstFile);
                            }
                        }
                        catch (Exception) { }
                    }
                });
                generator.unlock_stop();
            }
        }

        [Rpc.Methods.AllowBind(rename: "WriteFileMT")]
        public async Task<bool> WriteFileMT(long[] sessToken, bool force, string dstFile, DateTime[] fileInfo, Rpc.Methods.Name readFile)
        {
            if (!force && File.Exists(dstFile))
            {
                Console.WriteLine($"{dstFile} 已存在\r\n");
                return false;
            }
            Rpc.Session session = Rpc.Session.Self;
            generator self = generator.self;
            Rpc.Session.CancelWait().AppendNotify(() => self.stop());
            Console.WriteLine($"Begin Recv {dstFile}");
            bool completed = false;
            FileStream file = null;
            try
            {
                generator.lock_stop();
                await generator.send_strand(ioStrand, delegate ()
                {
                    try
                    {
                        if (File.Exists(dstFile))
                        {
                            if (File.GetLastWriteTime(dstFile) >= fileInfo[0])
                            {
                                Console.WriteLine($"{dstFile} 忽略\r\n");
                                completed = true;
                                return;
                            }
                            else
                            {
                                Console.WriteLine($"{dstFile} 覆盖\r\n");
                            }
                        }
                        file = File.Open(dstFile, FileMode.Create);
                    }
                    catch (Exception) { }
                });
                generator.unlock_stop();
                if (null == file)
                {
                    return completed;
                }
                Rpc.Methods.Name recvName = Rpc.Methods.GenName;
                Rpc.Session[] sessions = Array.ConvertAll(sessToken, (long v) => sessDict[v]);
                wait_group recvWg = new wait_group();
                TransmitFile.RecvResult recvResult = new TransmitFile.RecvResult();
                tuple<Action, Delegate> recvHandler = TransmitFile.RecvHandler(file, recvResult, recvWg, ioStrand, null);
                try
                {
                    for (int i = 0; i < sessions.Length; i++)
                    {
                        sessions[i].Methods.Bind(recvName, recvHandler.value2, generator.self_strand());
                    }
                    Rpc.Result<bool> result = await session.Call<bool>(readFile, recvName);
                    if (!result.Success)
                    {
                        Console.WriteLine($"Fail Recv {dstFile}");
                        return false;
                    }
                    Console.WriteLine($"End Recv {dstFile}");
                    completed = recvResult.fileLength == recvResult.recvLength;
                    return completed;
                }
                finally
                {
                    generator.lock_stop();
                    recvHandler.value1();
                    for (int i = 0; i < sessions.Length; i++)
                    {
                        sessions[i].Methods.Remove(recvName);
                    }
                    await recvWg.wait();
                    generator.unlock_stop();
                }
            }
            catch (generator.stop_exception)
            {
                Console.WriteLine($"Cancel Recv {dstFile}");
                throw;
            }
            finally
            {
                generator.lock_stop();
                await generator.send_strand(ioStrand, delegate ()
                {
                    if (null != file)
                    {
                        try
                        {
                            file.Dispose();
                            if (completed)
                            {
                                File.SetLastWriteTime(dstFile, fileInfo[0]);
                                File.SetCreationTime(dstFile, fileInfo[1]);
                                File.SetLastAccessTime(dstFile, fileInfo[2]);
                            }
                            else
                            {
                                File.Delete(dstFile);
                            }
                        }
                        catch (Exception) { }
                    }
                });
                generator.unlock_stop();
            }
        }

        [Rpc.Methods.AllowBind(rename: "CaptureScreen")]
        public async Task CaptureScreen(Rpc.Methods.Name refresh)
        {
            Console.WriteLine("CaptureScreen");
            generator self = generator.self;
            Rpc.Session.CancelWait().AppendNotify(() => self.stop());
            Rectangle bounds = Screen.GetBounds(Point.Empty);
            Bitmap bitmap1 = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format24bppRgb);
            Bitmap bitmap2 = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format24bppRgb);
            Bitmap bitmap3 = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format24bppRgb);
            limit_chan<void_type> grabNtf = new limit_chan<void_type>(1);
            int grabCount = 0, lastCount = 0;
            new Task(delegate ()
            {
                while (!self.is_completed())
                {
                    try
                    {
                        using (Graphics g = Graphics.FromImage(bitmap1))
                        {
                            g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
                        }
                        lock (self)
                        {
                            grabCount++;
                            var t = bitmap1;
                            bitmap1 = bitmap2;
                            bitmap2 = t;
                        }
                        grabNtf.force_post(default);
                    }
                    catch (Exception)
                    {
                        System.Threading.Thread.Sleep(1);
                    }
                }
            }).Start();
            while (true)
            {
                await grabNtf.receive();
                lock (self)
                {
                    if (lastCount == grabCount)
                    {
                        continue;
                    }
                    lastCount = grabCount;
                    var t = bitmap3;
                    bitmap3 = bitmap2;
                    bitmap2 = t;
                }
                BitmapData bitmapData = bitmap3.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
                try
                {
                    Rpc.Session.Self.OverSend(bitmapData.Scan0, 0, bitmapData.Stride * bounds.Height, 1 << 20);
                    await Rpc.Session.Self.Call(refresh, bounds.Width, bounds.Height);
                }
                finally
                {
                    bitmap3.UnlockBits(bitmapData);
                }
            }
        }
    }

    class Program
    {
        static void Main(string[] ps)
        {
            string ip = ps[0];
            int port = int.Parse(ps[1]);
            work_service work = new work_service();
            work_strand strand = new work_strand(work);
            shared_strand ioStrand = new shared_strand();
            generator.go(strand, async delegate ()
            {
                generator.children sessionChild = new generator.children();
reaccep:
                socket_tcp.acceptor accep = new socket_tcp.acceptor();
                if (!accep.bind(ip, port, 100))
                {
                    await generator.sleep(3000);
                    goto reaccep;
                }
                Dictionary<long, Rpc.Session> sessDict = new Dictionary<long, Rpc.Session>();
                long tokenCount = system_tick.get_tick();
                while (true)
                {
                    socket_tcp socket = new socket_tcp();
                    if (!await accep.accept(socket))
                    {
                        return;
                    }
                    Rpc.Methods methods = new Rpc.Methods();
                    Rpc.Session session = new Rpc.Session(strand, socket, methods);
                    long token = tokenCount++;
                    sessDict.Add(token, session);
                    sessionChild.free_go(async delegate ()
                    {
                        Console.WriteLine($"new session {token}");
                        await generator.send_task(delegate ()
                        {
                            methods.BindObj("IO", new IOController
                            {
                                token = token,
                                ioStrand = ioStrand,
                                sessDict = sessDict,
                            });
                        });
                        session.Start();
                        await session.Join();
                        sessDict.Remove(token);
                        Console.WriteLine($"free session {token} {session.Code}");
                    });
                }
            });
            work.run();
        }
    }
}
